package com.example.shoputils.id;

/**
 * @Author 刘武贵
 * @Date 2024/2/26 23:56
 * @Description 功能描述：雪花算法生成分布式序列号
 * 整个分布式id最核心的类SnowFlake，SnowFlake类主要是使用Java实现了雪花算法
 */
public class SnowFlake {
    /**
     * 起始的时间戳:2022-04-12 11:56:45，使用时此值不可修改
     * 注意：雪花算法的实现强依赖时间戳，所以在SnowFlake源码中存在如下常量，并标注了使用时此值不可更改的注释。
     */
    private final static long START_STAMP = 1649735805910L;

    /**
     * 每一部分占用的位数
     */
    // 序列号占用的位数
    private final static long SEQUENCE_BIT   = 12;
    // 机器标识占用的位数
    private final static long MACHINE_BIT    = 5;
    // 数据中心占用的位数
    private final static long DATACENTER_BIT = 5;

    /**
     * 每一部分的最大值
     */
    // 最大数据中心数量
    private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);
    // 最大机器数量
    private final static long MAX_MACHINE_NUM    = -1L ^ (-1L << MACHINE_BIT);
    // 最大序列号
    private final static long MAX_SEQUENCE       = -1L ^ (-1L << SEQUENCE_BIT);

    /**
     * 每一部分向左的位移
     */
    // 机器位
    private final static long MACHINE_LEFT    = SEQUENCE_BIT;
    // 数据中心位
    private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
    // 时间戳位
    private final static long TIMESTMP_LEFT   = DATACENTER_LEFT + DATACENTER_BIT;

    // 数据中心
    private long datacenterId;
    // 机器标识
    private long machineId;
    // 序列号
    private long sequence = 0L;
    // 上一次时间戳
    private long lastStmp = -1L;

    /**
     * 构造函数，用于初始化SnowFlake实例
     * @param datacenterId 数据中心ID
     * @param machineId 机器ID
     * 其中，第一个参数datacenterId表示数据中心id，也可以认为是机房的id，machineId表示机器id，
     * 也可以认为是服务所在的服务器id。在SnowFlake的构造方法中，对这两个参数进行了限制，如下所示。
     *
     * datacenterId：大于或者等于0，并且要小于MAX_DATACENTER_NUM，也就是小于31。
     * machineId：大于或者等于0，并且要小于MAX_DATACENTER_NUM，也就是小于31。
     * 所以，类实现的雪花算法支持32个不同的数据中心或机房，并且在每个数据中心或机房中支持32个机器上部署分布式id服务。这对一般的场景来说，已经足够了。
     *
     */
    public SnowFlake(long datacenterId, long machineId) {
        // 检查数据中心ID是否超出范围
        if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
            throw new IllegalArgumentException("datacenterId不能大于MAX_DATACENTER_NUM或小于0");
        }
        // 检查机器ID是否超出范围
        if (machineId > MAX_MACHINE_NUM || machineId < 0) {
            throw new IllegalArgumentException("machineId不能大于MAX_MACHINE_NUM或小于0");
        }
        // 初始化数据中心ID和机器ID
        this.datacenterId = datacenterId;
        this.machineId = machineId;
    }

    /**
     * 获取下一个ID
     * @return 下一个ID
     */
    public synchronized long nextId() {
        // 获取当前时间戳
        long currStmp = getNewstmp();
        // 如果当前时间戳小于上一次生成ID的时间戳，则抛出异常
        if (currStmp < lastStmp) {
            throw new RuntimeException("Clock moved backwards.  Refusing to generate id");
        }

        // 如果当前时间戳等于上一次生成ID的时间戳
        if (currStmp == lastStmp) {
            // 在相同毫秒内，序列号自增
            sequence = (sequence + 1) & MAX_SEQUENCE;
            // 同一毫秒的序列数已经达到最大
            if (sequence == 0L) {
                // 获取下一个时间戳
                currStmp = getNextMill();
            }
        } else {
            // 在不同毫秒内，序列号置为0
            sequence = 0L;
        }

        // 更新上一次生成ID的时间戳
        lastStmp = currStmp;

        // 返回ID
        return (currStmp - START_STAMP) << TIMESTMP_LEFT
                // 数据中心部分
                | datacenterId << DATACENTER_LEFT
                // 机器标识部分
                | machineId << MACHINE_LEFT
                // 序列号部分
                | sequence;
    }

    /**
     * 获取下一个时间戳
     * @return 下一个时间戳
     */
    private long getNextMill() {
        // 获取新的时间戳
        long mill = getNewstmp();
        // 如果时间戳小于等于上一个时间戳，则继续获取新的时间戳
        while (mill <= lastStmp) {
            mill = getNewstmp();
        }
        // 返回下一个时间戳
        return mill;
    }

    private long getNewstmp() {
        return System.currentTimeMillis();
    }

    public static Long getMaxDataCeneterNum() {
        return MAX_DATACENTER_NUM;
    }

    public static Long getMaxMachineNum() {
        return MAX_MACHINE_NUM;
    }

    /**
     * 主函数入口
     * @param args 命令行参数
     */
    public static void main(String[] args) {
        // 打印最大数据中心数量
        System.out.println(MAX_DATACENTER_NUM);
        // 打印最大机器数量
        System.out.println(MAX_MACHINE_NUM);
    }

}
